package ru.reaperoq.schema

import at.favre.lib.crypto.bcrypt.BCrypt
import kotlinx.serialization.Serializable
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
import org.jetbrains.exposed.sql.`java-time`.CurrentDateTime
import org.jetbrains.exposed.sql.`java-time`.datetime
import org.jetbrains.exposed.sql.transactions.transaction
import ru.reaperoq.utils.dbQuery
import ru.reaperoq.utils.toEpochMillis

@Serializable
data class User(
    val id: Int,
    val username: String,
    val password: String,
    val role: UserRole,
    val createdAt: Long,
    val locationId: Int? = null
)

object Users : Table("users") {
    val id = integer("id").autoIncrement().index()
    val username = varchar("username", 50).uniqueIndex()
    val passwordHash = varchar("password_hash", 255)
    val role = enumerationByName("role", 20, UserRole::class)
    val createdAt = datetime("created_at").defaultExpression(CurrentDateTime())
    val locationId = reference("location_id", Locations.id, onDelete = ReferenceOption.SET_NULL).nullable()

    override val primaryKey = PrimaryKey(id)
}

enum class UserRole {
    ADMIN, // Администратор, может управлять всеми пользователями, компьютерами и компонентами
    MODERATOR, // Модератор, может управлять компьютерами и компонентами
    USER // Пользователь, может просматривать компьютеры и компоненты
}

class UserService(private val database: Database) {
    init {
        transaction(database) {
            SchemaUtils.create(Users)
        }
    }

    suspend fun create(login: String, password: String): Int = dbQuery {
        Users.insert {
            it[username] = login
            it[passwordHash] = BCrypt.withDefaults().hashToString(6, password.toCharArray())
            it[role] = UserRole.USER
        }[Users.id]
    }

    suspend fun read(id: Int): User? {
        return dbQuery {
            Users.select { Users.id eq id }
                .map {
                    User(
                        it[Users.id],
                        it[Users.username],
                        it[Users.passwordHash],
                        it[Users.role],
                        it[Users.createdAt].toEpochMillis(),
                        it[Users.locationId]
                    )
                }
                .singleOrNull()
        }
    }

    suspend fun readByUsername(username: String): User? {
        return dbQuery {
            Users.select { Users.username eq username }
                .map {
                    User(
                        it[Users.id],
                        it[Users.username],
                        it[Users.passwordHash],
                        it[Users.role],
                        it[Users.createdAt].toEpochMillis(),
                        it[Users.locationId]
                    )
                }
                .singleOrNull()
        }
    }

    suspend fun update(id: Int, role: UserRole? = null, locationId: Int? = null) {
        dbQuery {
            Users.update({ Users.id eq id }) {
                role?.let { r ->
                    it[Users.role] = r
                }
                locationId?.let { l ->
                    it[Users.locationId] = l
                }
            }
        }
    }

    suspend fun readAll(): List<User> {
        return dbQuery {
            Users.selectAll()
                .map {
                    User(
                        it[Users.id],
                        it[Users.username],
                        it[Users.passwordHash],
                        it[Users.role],
                        it[Users.createdAt].toEpochMillis(),
                        it[Users.locationId]
                    )
                }
        }
    }

    suspend fun delete(id: Int) {
        dbQuery {
            Users.deleteWhere { Users.id.eq(id) }
        }
    }
}